/*!  \file  ImplementationFactory.cpp
 *	  \brief  
 * 
 *   \details   details
 *   \author    Yordan Madzhunkov German Research School for Simulation Sciences GmbH
 *   \version   1.0
 *   \date      04/24/2013 10:51:01 AM
 *   \copyright GNU Public License.
 */

#include <string.h>
#include <assert.h>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <ctime>
#include <iomanip>
#include "ImplementationFactory.h"
// Define what kind of Map we are going to use
#include "Map.h"
#include "FullSVD.h"
#include "FormSVD.h"
#include "TriEigen.h"
#include "BiSVD.h"
#include "BiReduction.h"
#include "Timer.h"
#include "UnitTester.h"
using namespace std;
Implementation::Implementation(const char * name_, const unsigned int group_) : name(NULL), group(group_), extraMemory(0), maxMemory(0), nThreads(1), sanity(false), tested(false), canBeParallel(false){
	setName(name_);
	setCompileTime(__DATE__, __TIME__);
};
void Implementation::setName 	(const char * val){ 
	Test::set(name       , val);
}

ImplementationFactory * ImplementationFactory::myInstance = 0;
ImplementationFactory * ImplementationFactory::instance() {
	if (!myInstance) 
		myInstance = new ImplementationFactory(); 
	return myInstance;
};
ImplementationFactory::ImplementationFactory() {
	nGroups = 0; nImplementations = 0;
	memset(groups    , 0, sizeof(groups[0]    ) * MaxGroups);
	memset(bluePrints, 0, sizeof(bluePrints[0]) * MaxGroups);
	memset(benchmarks, 0, sizeof(benchmarks[0]) * MaxGroups);
	memset(unitTests , 0, sizeof( unitTests[0]) * MaxGroups);

}

ImplementationFactory::~ImplementationFactory() {
	for (unsigned int i = 0 ; i < MaxGroups; i++) {
		if (    groups[i]) delete     groups[i];     groups[i] = 0;
		if (bluePrints[i]) delete [] bluePrints[i]; bluePrints[i] = 0;
		if (benchmarks[i]) delete [] benchmarks[i]; benchmarks[i] = 0;
		if ( unitTests[i]) delete []  unitTests[i];  unitTests[i] = 0;
	}
}


// We provide the factory with the necessery information how to make new products
void ImplementationFactory::registerImplementation(Implementation & newImplementation ) {
	nImplementations++;
	unsigned int gID = newImplementation.getGroup();
	if (gID < MaxGroups ) {
		if (!groups[gID]) {
			groups[gID] = new Map();
			assert(bluePrints[gID] == NULL);
			bluePrints[gID] = new Implementation* [Map::MaxID];
			memset(bluePrints[gID], 0, sizeof(bluePrints[gID][0]) * Map::MaxID);
			benchmarks[gID] = new Benchmark     * [Map::MaxID];
			memset(benchmarks[gID], 0, sizeof(benchmarks[gID][0]) * Map::MaxID);
			unitTests [gID] = new UnitTest      * [Map::MaxID];
			memset( unitTests[gID], 0, sizeof( unitTests[gID][0]) * Map::MaxID);
		}
		unsigned int iID = groups[gID]->insert(newImplementation.getName());
		bluePrints[gID][iID] = &newImplementation;
		switch (gID) {
			case BiReductionGroup       : { 
				BiReduction & reduction = dynamic_cast<BiReduction&>(
																newImplementation);
//				benchmarks[gID][iID] = new BiReductionBenchmark(reduction);
				unitTests [gID][iID] = new BiReductionUnitTest (reduction);
				break;
			};
			case BiSVDGroup             : { 
				BiSVD & bisvd = dynamic_cast<BiSVD&>(newImplementation);
				benchmarks[gID][iID] = new BiSVDBenchmark(bisvd);
				if (bisvd.getCanBeParallel())
					benchmarks[gID][iID]->setNext(new BiSVDScaling(bisvd));
				unitTests [gID][iID] = new BiSVDUnitTest (bisvd);
				break;
			};
			case FullSVDGroup           : { 
				FullSVD & svd = dynamic_cast<FullSVD&>(newImplementation);
				benchmarks[gID][iID] = new FullSVDBenchmark(svd);
				if (svd.getCanBeParallel())
					benchmarks[gID][iID]->setNext(new FullSVDScaling(svd));
				unitTests [gID][iID] = new FullSVDUnitTest (svd);
				break;
			};
			case TriEigenGroup          : { 
				TriEigen & ev = dynamic_cast<TriEigen&>(newImplementation);
				unitTests [gID][iID] = new TriEigenUnitTest (ev);
				break;
			};
			case FormSVDGroup : { 
				FormSVD & form = dynamic_cast<FormSVD&>(newImplementation);
				unitTests [gID][iID] = new FormSVDUnitTest (form);
				break;
			};
			default: { MISSING;};	
		}			

	
	} else {
		cerr << "[ImplementationFactory::registerImplementation] GroupID outside allowed range" << endl;
		exit(-1);
	}
}

void ImplementationFactory::deregisterImplementation(Implementation & newImplementation ) {
	nImplementations--;
	unsigned int gID = newImplementation.getGroup();
	assert(groups[gID] != NULL);
	unsigned int iID = groups[gID]->getID(newImplementation.getName());
	assert(bluePrints[gID] != NULL);
	bluePrints[gID][iID] = 0;
	switch (gID) {
		case BiReductionGroup       : { 
			if (unitTests [gID][iID])
				delete dynamic_cast<BiReductionUnitTest*>(unitTests [gID][iID]);
			unitTests [gID][iID] = 0;
			break;
		};
		case BiSVDGroup             : { 
			if (benchmarks[gID][iID])
				delete dynamic_cast<BiSVDBenchmark*>(benchmarks[gID][iID]);
			benchmarks[gID][iID] = 0;
			if (unitTests[gID][iID])
				delete dynamic_cast<BiSVDUnitTest *>(unitTests[gID][iID]);
			unitTests[gID][iID] = 0;
			break;
		};
		case FullSVDGroup           : { 
			if (benchmarks[gID][iID])
				delete dynamic_cast<FullSVDBenchmark*>(benchmarks[gID][iID]);
			benchmarks[gID][iID] = 0;
			if (unitTests[gID][iID])
				delete dynamic_cast<FullSVDUnitTest *>(unitTests[gID][iID]);
			unitTests[gID][iID] = 0;
			break;
		};
		case TriEigenGroup          : { 
			if (unitTests[gID][iID])
				delete dynamic_cast<TriEigenUnitTest *>(unitTests[gID][iID]);
			unitTests[gID][iID] = 0;
			break;
		};
		case FormSVDGroup : {
			if (unitTests [gID][iID])
				delete dynamic_cast<FormSVDUnitTest*>(unitTests [gID][iID]);
			unitTests [gID][iID] = 0;
			break;
		};
		default: { MISSING;};	
	}			

	if (nImplementations == 0) {
		delete myInstance;
	}
}

void ImplementationFactory::printAllRegistered() const {
	cout << endl << "ImplementationFactory has " << nImplementations << 
	" implementations" << endl;
	const char * groupLabels[] = {
	 "Reduction from General to BiDiagonal matrix", 
	 "SVD of Bidiagonal matrix", 
	 "Forming the full SVD",
	 "SVD of General matrix", 
	 "Symmetric Tridiagunal Eigenvalues"};
	for (unsigned int i = 0; i < getMaxGroups(); i++) {
		if (groups[i]) {
			cout << "  " << groupLabels[i] << endl;
			for (unsigned int j = 0; j < Map::MaxID; j++)
				if (groups[i]->getName(j)) {
					cout << setw(5) << j << " ";
					if (unitTests[i][j]) {
						if( unitTests[i][j]->isPassed() )
							cout << "\033[0;32m";
						else 
							cout << "\033[0;31m";
					} else {
						cout << "\033[0;33m";
					}
					cout << setw(20) << groups[i]->getName(j) << "\033[0m";
					if (bluePrints[i][j]->getCanBeParallel()) 
						cout << " [parallel] ";
					else 
						cout << " [serial  ] ";
					cout << endl;
				}
		}
	}
}
void ImplementationFactory::printGroupPlotFiles() const {
	cout << "Printing group plot files" << endl;
	for (int i= 0; i < MaxGroups; i++)	printGroupPlotFiles(i); 
}
void ImplementationFactory::printGroupPlotFiles(unsigned int group) const {
	if (!bluePrints[group] || !unitTests[group] || !benchmarks[group]) return;
	int nIDs = 0;
	int IDs[Map::MaxID];
	for (int i = 0; i < Map::MaxID; i++) 
		if (bluePrints[group][i] && benchmarks[group][i] &&unitTests[group][i]) {
			if (unitTests[group][i]->isPassed() && 
//				benchmarks[group][i]->isPassed() &&
				benchmarks[group][i]->allDatFilesArePresent()) {
				IDs[nIDs++] = i;
			}
		}

	if (nIDs > 0) {
		Benchmark & b0 = *benchmarks[group][IDs[0]];
		for (int i = 0; i < b0.getNMaxMetrics() ; i++) {
			const char * metricName = b0.getMetricName(i);
			const char * metricUnits = NULL;

			if (metricName) {
				char * commonPlotFileName = b0.getMetricFileName(metricName, 
					UnitTester::instance()->getBenchmarkDirectory(), 
					".gnuplot", b0.getBaseName());
				char * plotOut = b0.getMetricFileName(metricName, "./", 
					".png", b0.getBaseName());
				ofstream fout(commonPlotFileName);

				fout << "set terminal png\n";
				fout << "set output '" << plotOut << "'\n";
				fout << "set boxwidth 0.2 absolute\n";
				fout << "set title \"" << b0.getBaseName() << " ";
				fout << metricName << "\" \n"; 
				fout << "set autoscale\n";
				fout << "set xlabel \"Configuration Index\"\n";
				fout << "set ylabel \"" << metricName;
				if (metricUnits) 	
					fout << "(" << metricUnits << ")";
				fout << "\"\n";

				fout << "plot "; 
				for (int j = 0; j < nIDs; j++) {
					Benchmark      & bench = *benchmarks[group][IDs[j]];
					Implementation &  impl = *bluePrints[group][IDs[j]];
				
					const char * datF = bench.getMetricFileName(metricName, "./");
					fout << "'" << datF << "' using 1:5:4:8:7 with candlesticks title '";
					fout << impl.getName() << "' whiskerbars";
//					fout << ", '' using 1:6:6:6:6 with candlesticks lt -1 notitle";
					if (j < nIDs - 1) fout << ", ";
					if (datF) delete [] datF; datF = 0;
				}	
				if (commonPlotFileName) delete [] commonPlotFileName;
				commonPlotFileName = NULL;
				if (plotOut) delete [] plotOut;	plotOut = NULL;
			}
		}

	}
}
Implementation * ImplementationFactory::first(unsigned int group) const {
	if (groups[group]) {
		unsigned int ID = groups[group]->first();
		return ID != Map::MaxID ? bluePrints[group][ID] : NULL;
	}
	return NULL;
}
Implementation * ImplementationFactory::next (unsigned int group) const {
	if (groups[group]) {
		unsigned int ID = groups[group]->next();
		return ID != Map::MaxID ? bluePrints[group][ID] : NULL;
	}
	return NULL;
}

